﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;
using HeapSpecSupport;
using SimpleIR;

namespace Evaluator.EvalState
{
	/// <summary>
	/// This struct represents pointer typed values.
	/// </summary>
	internal sealed class EPointer : EMemoryValue
	{
		/// <summary>
		/// The object this is a pointer to.
		/// </summary>
		private readonly int m_trgtobj;

		private static Dictionary<IRPtrType, EPointer> s_defaultpointer = new Dictionary<IRPtrType,EPointer>();

		/// <summary>
		/// Create a pointer typed value.
		/// </summary>
		internal EPointer(IRPtrType ptrtype, int tobj)
			: base(ptrtype)
		{ this.m_trgtobj = tobj; }

		internal static EPointer CreateDefaultNullPointer(IRPtrType ptrtype)
		{
			Contract.Requires(ptrtype != null);

 			if(!s_defaultpointer.ContainsKey(ptrtype))
				s_defaultpointer.Add(ptrtype, new EPointer(ptrtype, -1));

			return s_defaultpointer[ptrtype];
		}

		/// <summary>
		/// Convert the type of the pointer (does not do any dynamic checks).
		/// </summary>
		internal EPointer ConvertPointerType(IRPtrType newtype)
		{
			Contract.Requires(newtype != null);

			return new EPointer(newtype, this.m_trgtobj);
		}

		public override string ToString()
		{ return String.Format("{0}", this.m_trgtobj.ToString()); }

		/// <summary>
		/// Return the <c>ENumeric</c> encoding of the result of testing the two pointers for equality
		/// </summary>
		internal static ENumeric PointerEqualsTest(EPointer p1, EPointer p2)
		{ return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, p1.m_trgtobj == p2.m_trgtobj); }

		/// <summary>
		/// Return the <c>ENumeric</c> encoding of the result of testing the pointer for nullity
		/// </summary>
		internal ENumeric PointerNullTest()
		{ return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_trgtobj == -1); }

		internal IRPtrType PointerType
		{ get { return (IRPtrType)this.m_valuetype; } }

		internal int TargetObject
		{ get { return this.m_trgtobj; } }
	}

	/// <summary>
	/// This class represents reference typed values.
	/// </summary>
	internal sealed class EReference : EValue 
	{
		/// <summary>
		/// The enclosing location that holds whatever the reference refers to.
		/// </summary>
		private readonly int m_trgtlocation;

		/// <summary>
		/// If the target is an array this is the initial array index (otherwise it is -1)
		/// </summary>
		private readonly int m_initialindex;

		/// <summary>
		/// This is the remaining access path to the reference target (it is empty if the reference refers to the outer location).
		/// </summary>
		private readonly IRBasicAccessPath m_accp;

		private static Dictionary<IRReferenceType, EReference> s_defaultrefs = new Dictionary<IRReferenceType, EReference>();

		/// <summary>
		/// Create a reference typed value.
		/// </summary>
		internal EReference(IRReferenceType reftype, int taddr, int arridx, IRBasicAccessPath accp)
			: base(reftype)
		{
			Contract.Requires(accp != null);
			Contract.Invariant((accp == IRBasicAccessPath.EmptyAccessPath) | (reftype.DerefTargetType == accp.PathLast.FieldType));

			this.m_trgtlocation = taddr;
			this.m_initialindex = arridx;
			this.m_accp = accp;
		}

		internal static EReference CreateDefaultNullReference(IRReferenceType reftype)
		{
			Contract.Requires(reftype != null);

			if(!s_defaultrefs.ContainsKey(reftype))
				s_defaultrefs.Add(reftype, new EReference(reftype, -1, -1, IRBasicAccessPath.EmptyAccessPath));

			return s_defaultrefs[reftype];
		}

		public override string ToString()
		{return String.Format("{0} ({1})", this.m_trgtlocation.ToString(), (this.m_initialindex != -1 ? "" : ("[" + this.m_initialindex.ToString() + "]")) + this.m_accp.ToString());}

		/// <summary>
		/// Return the <c>ENumeric</c> encoding of the result of testing the two references for equality
		/// </summary>
		internal static ENumeric ReferenceEqualsTest(EReference r1, EReference r2)
		{
			bool eq = (r1.m_trgtlocation == r2.m_trgtlocation) & (r1.m_initialindex == r2.m_initialindex) & (r1.m_accp == r2.m_accp);
			return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, eq); 
		}

		/// <summary>
		/// Return the <c>ENumeric</c> encoding of the result of testing the reference for nullity
		/// </summary>
		internal ENumeric ReferenceNullTest()
		{ return new ENumeric(ExeProgram.TProg.TypeTable.Int64Type, this.m_trgtlocation == -1); }

		/// <summary>
		/// Return the result of indexing the reference by the given field offset.
		/// </summary>
		internal EReference IndexAddrByField(IRMemberField mf)
		{
			Contract.Requires(mf != null);
			Contract.Requires(this.m_accp == IRBasicAccessPath.EmptyAccessPath || this.m_accp.PathLast.FieldType.TypeDescription.AllVisibleFields.Contains(mf));

			return new EReference(ExeProgram.TProg.TypeTable.LookupReferenceTypeFor(mf.FieldType), this.m_trgtlocation, this.m_initialindex, ExeProgram.TProg.GetAccessPathExtensionPost(this.m_accp, mf));
		}

		/// <summary>
		/// Return the result of indexing the reference by the given array index.
		/// </summary>
		internal EReference IndexAddrByIndex(int index, IRMemberField contentsfield)
		{
			Contract.Requires(contentsfield != null);
			Contract.Assert(this.m_accp == IRBasicAccessPath.EmptyAccessPath || (this.m_accp.Path.Length == 1 && this.m_accp.PathFirst == contentsfield));

			int newidx = (this.m_initialindex != -1) ? (this.m_initialindex + index) : index;
			IRBasicAccessPath nbap = ExeProgram.TProg.GetAccessPathForSingleField(contentsfield);
			return new EReference(ExeProgram.TProg.TypeTable.LookupReferenceTypeFor(contentsfield.FieldType), this.m_trgtlocation, newidx, nbap);
		}

		internal IRReferenceType ReferenceType
		{ get { return (IRReferenceType)this.m_valuetype; } }

		internal int TargetLocation
		{ get { return this.m_trgtlocation; } }

		internal int InitialArrayIndex
		{ get { return this.m_initialindex; } }

		internal IRBasicAccessPath Path
		{ get { return this.m_accp; } }
	}
}
